<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>call</title>
</head>
<body>
    <script>
        /**
         * @author      haiyuan.wang
         * @date        2021.04.01
         * @vision      1.0
         * 
         * @description call                                 方法主要的作用是改变函数的执行环境
         * @example     fn.call(thisArg, arg1, arg2, ...)
         * @param       {Object}    thisArg                  必选的。运行时的 this 值，非严格模式下，null 和 undefined 会自动指向 window
         * @param       {*}         arg1, arg2, ...          可选的。执行过程中要传入的参数
        */

        // 第一个例子，使用 call 方法调用父构造方法，实现继承
        // 基类 Goods
        function Goods(goods, amount, price) {
            this.goods = goods
            this.amount = amount
            this.price = price
        }
        // 子类，继承基类的属性
        function Fruit(goods, amount, price) {
            Goods.call(this, goods, amount, price)
        }
        let fruit = new Fruit('orange', 100, 50)
        console.log('fruit :>> ', fruit); // Fruit { goods: 'orange', amount: 100, price: 50 }


        // 第二个例子，使用 call 调用函数的，并且指定上下文的 this
        let tempObj = {
            language: 'JavaScript',
        }
        function getLanguage() {
            return this.language
        }
        console.log(getLanguage.call(tempObj)); // JavaScript

        // 实现一个自己的 call 方法
        ;(function() {
            if(!Function.prototype.myCall) { // 给 Fuction 类的原型上加 myCall 属性
                Function.prototype.myCall = function myCall(context, ...args) {
                    let fn = this;      // 谁调用myCall谁就是this，所以this就是原函数
                    console.log(fn);    // getLanguage 函数
                    if(typeof fn !== 'function') throw TypeError `${fn} is not a funtion` // 如果不是函数，抛出一个类型错误
                    let key = Symbol('key') 
                    if (typeof context === 'undefined' || context === null) {  // 判断是否是undefined和null
                        context = window
                    }
                    context[key] = fn    // 将 fn 挂在到 context 的属性上，通过 context.fn() 这种形式调用，就实现了改变this(谁调用fn，谁就是this)       
                    let result = context[key](...args)
                    delete context[key]
                    return result
                }
            }
        })()
        console.log(getLanguage.myCall(tempObj)); // JavaScript
        // console.log(/sdf/.myCall(tempObj)); // /sdf/.myCall is not a function
        // 复杂数据类型 Object: Date  regExp  function  Array
        // 基本数据类型： string number Boolean null undefined


    </script>
</body>
</html>